﻿using System;
using SoftwareConsulting.BI11.Studio.InternalServices;

namespace SoftwareConsulting.BI11.NumericalMethods
{    
    /// <summary>
    /// Класс, реализующий дополнительные математические функции и определяющий параметры представления плавающей точки
    /// </summary>
    public static class MathExtended
    {        
        private static double _defaultNumericalPrecision = 0;//типичная значимая точность для численных расчетов        
        private static double _smallNumber = 0;//типичное значимое маленькое число для численных расчетов        
        private static int _radix = 0;//основание системы счисления для чисел с плавающей точкой        
        private static double _machinePrecision = 0;//наибольшее положительное значение, дающее ноль, при добавлении к 1.0
        private static double _negativeMachinePrecision = 0;//наибольшее положительное значение, дающее ноль, при вычитании из 1.0        
        private static double _smallestNumber = 0;//наименьшее число, отличное от нуля        
        private static double _largestNumber = 0;//наибольшее положительное число        
        private static double _largestExponentialArgument = 0;//наибольший аргумент для экспоненты        
        private static double[] _scales = { 1.25, 2, 2.5, 4, 5, 7.5, 8, 10 }; //значения для вычисления читабельных человеком масштабов
        private static double[] _semiIntegerScales = { 2, 2.5, 4, 5, 7.5, 8, 10 }; //значения для вычисления читабельных человеком масштабов
        private static double[] _integerScales = { 2, 4, 5, 8, 10 }; //значения для вычисления читабельных человеком масштабов

        /// <summary>
        /// Вычисляет наибольшее положительное число
        /// </summary>
        private static void ComputeLargestNumber()
        {
            double floatingRadix = Radix;
            double fullMantissaNumber = 1.0d -
                                floatingRadix * NegativeMachinePrecision;
            while (!Double.IsInfinity(fullMantissaNumber))
            {
                _largestNumber = fullMantissaNumber;
                fullMantissaNumber *= floatingRadix;
            }
        }

        /// <summary>
        /// Вычисляет наибольшее положительное значение, дающее ноль, при добавлении к 1.0
        /// </summary>
        private static void ComputeMachinePrecision()
        {
            double floatingRadix = Radix;
            double inverseRadix = 1.0d / floatingRadix;
            _machinePrecision = 1.0d;
            double tmp = 1.0d + _machinePrecision;
            while (tmp - 1.0d != 0.0d)
            {
                _machinePrecision *= inverseRadix;
                tmp = 1.0d + _machinePrecision;
            }
        }
        
        /// <summary>
        /// Вычисляет наибольшее положительное значение, дающее ноль, при вычитании из 1.0
        /// </summary>
        private static void ComputeNegativeMachinePrecision()
        {
            double floatingRadix = Radix;
            double inverseRadix = 1.0d / floatingRadix;
            _negativeMachinePrecision = 1.0d;
            double tmp = 1.0d - _negativeMachinePrecision;
            while (tmp - 1.0d != 0.0d)
            {
                _negativeMachinePrecision *= inverseRadix;
                tmp = 1.0d - _negativeMachinePrecision;
            }
        }

        /// <summary>
        /// Вычисляет основание системы счисления для чисел с плавающей точкой
        /// </summary>
        private static void ComputeRadix()
        {
            double a = 1.0d;
            double tmp1, tmp2;
            do
            {
                a += a;
                tmp1 = a + 1.0d;
                tmp2 = tmp1 - a;
            } while (tmp2 - 1.0d != 0.0d);
            double b = 1.0d;
            while (_radix == 0)
            {
                b += b;
                tmp1 = a + b;
                _radix = (int)(tmp1 - a);
            }
        }

        /// <summary>
        /// Вычисляет наименьшее число, отличное от нуля
        /// </summary>
        private static void ComputeSmallestNumber()
        {
            double floatingRadix = Radix;
            double inverseRadix = 1.0d / floatingRadix;
            double fullMantissaNumber = 1.0d - floatingRadix * NegativeMachinePrecision;
            while (fullMantissaNumber != 0.0d)
            {
                _smallestNumber = fullMantissaNumber;
                fullMantissaNumber *= inverseRadix;
            }
        }

        /// <summary>
        /// Возвращает типичную значимую точность для численных расчетов
        /// </summary>
        public static double DefaultNumericalPrecision
        {
            get
            {
                if (_defaultNumericalPrecision == 0)
                    _defaultNumericalPrecision = Math.Sqrt(MachinePrecision);
                return _defaultNumericalPrecision;
            }
        }

        /// <summary>
        /// Возвращает флаг того, что разница между указанными числами не превышает типичную значимую точность для численных расчетов
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool Equal(double a, double b)
        {
            return Equal(a, b, DefaultNumericalPrecision);
        }

        /// <summary>
        /// Возвращает флаг того, что разница между указанными числами не превышает указанную точность
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static bool Equal(double a, double b, double precision)
        {
            double norm = Math.Max(Math.Abs(a), Math.Abs(b));
            return norm < precision || Math.Abs(a - b) < precision * norm;
        }

        /// <summary>
        /// Возвращает наибольший аргумент для экспоненты
        /// </summary>
        public static double LargestExponentialArgument
        {
            get
            {
                if (_largestExponentialArgument == 0)
                    _largestExponentialArgument = Math.Log(LargestNumber);
                return _largestExponentialArgument;
            }
        }

        /// <summary>
        /// Возвращает наибольшее положительное число
        /// </summary>
        public static double LargestNumber
        {
            get
            {
                return double.MaxValue;
            }
        }

        /// <summary>
        /// Возвращает наибольшее положительное значение, дающее ноль, при добавлении к 1.0
        /// </summary>
        public static double MachinePrecision
        {
            get
            {
                if (_machinePrecision == 0)
                    ComputeMachinePrecision();
                return _machinePrecision;
            }
        }

        /// <summary>
        /// Возвращает наибольшее положительное значение, дающее ноль, при вычитании из 1.0
        /// </summary>
        public static double NegativeMachinePrecision
        {
            get
            {
                if (_negativeMachinePrecision == 0)
                    ComputeNegativeMachinePrecision();
                return _negativeMachinePrecision;
            }
        }

        /// <summary>
        /// Возвращает основание системы счисления для чисел с плавающей точкой
        /// </summary>
        public static int Radix
        {
            get
            {
                if (_radix == 0)
                    ComputeRadix();
                return _radix;
            }
        }

        /// <summary>
        /// Возвращает наименьшее число, отличное от нуля
        /// </summary>
        public static double SmallestNumber
        {
            get
            {
                return double.Epsilon;
            }
        }

        /// <summary>
        /// Сбрасывает все вычисленные параметры
        /// </summary>
        public static void Reset()
        {
            _defaultNumericalPrecision = 0;
            _smallNumber = 0;
            _radix = 0;
            _machinePrecision = 0;
            _negativeMachinePrecision = 0;
            _smallestNumber = 0;
            _largestNumber = 0;
        }
        
        /// <summary>
        /// Возвращает указанное число округленное до ближайшего целого  указанного масштаба
        /// </summary>
        /// <param name="value"></param>
        /// <param name="scale"></param>
        /// <returns></returns>
        public static double RoundTo(double value, double scale)
        {
            return Math.Round(value / scale) * scale;
        }
        
        /// <summary>
        /// Возвращает указанное число, округленное вверх до следующего значения масштаба
        /// </summary>
        /// <param name="value"></param>
        /// <param name="isIntegerValue"></param>
        /// <returns></returns>
        public static double RoundToScale(double value, bool isIntegerValue)
        {
            double[] scaleValues;
            int orderOfMagnitude = (int)Math.Floor(Math.Log(value) / Math.Log(10.0));
            if (isIntegerValue)
            {
                orderOfMagnitude = Math.Max(1, orderOfMagnitude);
                if (orderOfMagnitude == 1)
                    scaleValues = _integerScales;
                else if (orderOfMagnitude == 2)
                    scaleValues = _semiIntegerScales;
                else
                    scaleValues = _scales;
            }
            else
                scaleValues = _scales;
            double exponent = Math.Pow(10.0, orderOfMagnitude);
            double rValue = value / exponent;
            foreach (double scaleValue in scaleValues)
            {
                if (rValue <= scaleValue)
                    return scaleValue * exponent;
            }
            return exponent;
        }

        /// <summary>
        /// Возвращает типичное значимое маленькое число для численных расчетов
        /// </summary>
        public static double SmallNumber
        {
            get
            {
                if (_smallNumber == 0)
                    _smallNumber = Math.Sqrt(SmallestNumber);
                return _smallNumber;
            }
        }

        /// <summary>
        /// Возвращает наибольшую степень двойки, меньшую чем указанное число
        /// </summary>
        /// <param name="highLimit"></param>
        /// <returns></returns>
        public static int LargestPowerOf2SmallerThan(int highLimit)
        {
            int maxValue = 10000000;
            Assert.FailIf(highLimit >= maxValue, Assert.Id.Assert00296);
            int m = 2;
            for (int i = 0; i < maxValue; i++)
            {
                int m2 = 2 * m;
                if (m2 >= highLimit)
                    return m;
                m = m2;
            }
            Assert.Fail(Assert.Id.Assert00296);
            return 0;
        }

        /// <summary>
        /// Возвращает факториал указанного числа
        /// </summary>
        /// <param name="n"></param>
        /// <returns></returns>
        public static long Factorial(long n)
        {
            return n < 2 ? 1 : n * Factorial(n - 1);
        }
    }
}
